package com.michaelbulava.TFS.ui.controls.connect;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import com.intellij.util.EventDispatcher;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.common.catalog.TeamProjectCollectionInfo;
import com.michaelbulava.TFS.configuration.ProjectPreferences;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.tasks.QueryTeamProjectsTask;
import com.michaelbulava.TFS.tasks.connect.ConnectToConfigurationServerTask;
import com.michaelbulava.TFS.ui.controls.generic.IProjectDependantControl;
import com.michaelbulava.TFS.ui.helpers.UIHelpers;
import com.microsoft.tfs.core.TFSConfigurationServer;
import com.microsoft.tfs.core.TFSConnection;
import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.commonstructure.ProjectInfo;
import com.microsoft.tfs.core.httpclient.CookieCredentials;
import com.microsoft.tfs.core.httpclient.Credentials;
import com.microsoft.tfs.core.httpclient.DefaultNTCredentials;
import com.microsoft.tfs.core.httpclient.UsernamePasswordCredentials;
import com.microsoft.tfs.core.util.serverlist.ServerListConfigurationEntry;
import com.microsoft.tfs.util.GUID;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.net.URI;
import java.util.*;

/**
 * Created by mbulava on 6/19/2015.
 */
public class TeamProjectSelectControl extends JPanel implements HierarchyListener, IProjectDependantControl {
    private ServerSelectControl selectServerControl;
    private ProjectCollectionTable projectCollectionTable;
    private TeamProjectTable teamProjectTable;
    private JButton changeCredentials;

    private boolean ignoreServerChangeEvents = false;

    private URI serverURI = null;
    private Credentials credentials = null;
    private TFSConfigurationServer configurationServer;
    //private TeamProjectCollectionInfo[] projectCollections = null;
    private TFSTeamProjectCollection collection;
    //private ICommandExecutor noErrorDialogCommandExecutor;
    //private ICommandExecutor commandExecutor = null;


    private Map<TFSTeamProjectCollection, ProjectInfo[]> teamProjectsForCollection = new HashMap<TFSTeamProjectCollection, ProjectInfo[]>();
    private final EventDispatcher<ChangeListener> myEventDispatcher = EventDispatcher.create(ChangeListener.class);
    private final EventDispatcher<ChangeListener> backgroundProcess = EventDispatcher.create(ChangeListener.class);

    public interface BackgroundProcessListener{
        void processStarted();
        void processCompleted();
    }

    private ProjectInfo[] teamProjects;
    private ProjectInfo[] selectedProjects;

    private boolean hasPainted = false;

    private boolean skipCollectionSelection = false;

    private Project myProject;
    private BackgroundProcessListener myBackgroundProcessListener;

    public interface ProjectSelectionChangedListener{
        void onProjectSelectionChanged();
    }

    public void setBackgroundProcessListener(BackgroundProcessListener listener){
        this.myBackgroundProcessListener = listener;
    }

    private ProjectSelectionChangedListener listener;

    public TeamProjectSelectControl(){
        super();
        setupUI();
        refresh();
        this.selectServerControl.populaterServersCombo(true);
    }

    private void setupUI(){
        setLayout(new GridLayoutManager(4, 2, new Insets(4, 4, 4, 4), -1, -1));
        selectServerControl = new ServerSelectControl();
        add(selectServerControl, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER,
                GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK |
                GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK |
                GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));

        selectServerControl.setSelectionChangedListener(new ServerSelectControl.SelectionChangedListener() {
            @Override
            public void onSelectionChanged(ServerListConfigurationEntry selectedConfigurationEntry) {
                if (!ignoreServerChangeEvents){
                    URI uri = null;
                    if (selectedConfigurationEntry != null)
                        uri = selectedConfigurationEntry.getURI();

                    setServerInternal(uri, null);

                }
            }
        });

        final JSplitPane splitPane1 = new JSplitPane();
        add(splitPane1, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null,
                new Dimension(200, 200), null, 0, false));
        final JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1));
        splitPane1.setLeftComponent(panel2);
        final JLabel label1 = new JLabel();
        label1.setText(Messages.getString("SelectTeamProjectControl.CollectionsLabelText"));
//        this.$$$loadLabelText$$$(label1, ResourceBundle.getBundle("com/MichaelBulava/tfs/vcsintegration/core/Messages")
//                .getString("SelectTeamProjectControl.CollectionsLabelText"));
        panel2.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JScrollPane scrollPane1 = new JScrollPane();
        panel2.add(scrollPane1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
                GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0,
                false));
        projectCollectionTable = new ProjectCollectionTable();
        scrollPane1.setViewportView(projectCollectionTable);

        ListSelectionModel cellSelectionModel = projectCollectionTable.getSelectionModel();
        cellSelectionModel.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                ListSelectionModel lsm = (ListSelectionModel)e.getSource();
                if (!lsm.isSelectionEmpty() && !skipCollectionSelection){
                    setTeamProjectCollectionInternal(projectCollectionTable.getSelectedCollection());
                    myEventDispatcher.getMulticaster().stateChanged(new ChangeEvent(this));
                }
            }
        });

        final JPanel panel3 = new JPanel();
        panel3.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1));
        splitPane1.setRightComponent(panel3);
        final JLabel label2 = new JLabel();
        label2.setText(Messages.getString("SelectTeamProjectControl.ProjectsLabelText"));
//        this.$$$loadLabelText$$$(label2, ResourceBundle.getBundle("com/MichaelBulava/tfs/vcsintegration/core/Messages")
//                .getString("SelectTeamProjectControl.ProjectsLabelText"));
        panel3.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JScrollPane scrollPane2 = new JScrollPane();
        panel3.add(scrollPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
                GridConstraints.FILL_BOTH
                , GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW
                , GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW
                , null, null, null, 0,
                false));
        teamProjectTable = new TeamProjectTable();
        scrollPane2.setViewportView(teamProjectTable);

        teamProjectTable.setCheckItemsStateChangeListener(new TeamProjectTable.CheckItemsStateChangedListener() {
            @Override
            public void onCheckItemsStateChanged() {
                if (listener != null){
                    listener.onProjectSelectionChanged();
                }
                myEventDispatcher.getMulticaster().stateChanged(new ChangeEvent(this));
            }
        });

        final Spacer spacer1 = new Spacer();
        add(spacer1, new GridConstraints(3, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1,
                GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        changeCredentials = new JButton();
        changeCredentials.setText(Messages.getString("SelectTeamProjectControl.CredentialsButtonText"));
//        this.$$$loadButtonText$$$(changeCredentials, ResourceBundle.getBundle("com/MichaelBulava/tfs/vcsintegration/core/Messages")
//                .getString("SelectTeamProjectControl.CredentialsButtonText"));
        add(changeCredentials, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer2 = new Spacer();
        add(spacer2, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
                GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        changeCredentials.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                useDifferentCredentials();
            }
        });

    }

    @Override
    public void setProject(Project project){
        myProject = project;
    }

    @Override
    public Project getProject() {
        return myProject;
    }


    public void setSelectedConnectionURI(URI serverURI){
        this.selectServerControl.setServerURI(serverURI);
    }

    public void setSelectedCollection(TFSTeamProjectCollection collection){
        projectCollectionTable.setTFSConnection(collection.getConfigurationServer());
        projectCollectionTable.setSelectedProjectCollection(collection.getInstanceID());
    }

    public void setSelectedProjects(ProjectInfo[] selectedProjects){
        teamProjectTable.setCheckedProjects(selectedProjects);
    }

    public void addChangeListener(ChangeListener value){
        myEventDispatcher.addListener(value);
    }

    public void setProjectSelectionChangedListener(ProjectSelectionChangedListener listener){
        this.listener = listener;
    }

    public ProjectInfo[] getSelectedTeamProjects(){
        return teamProjectTable.getCheckedProjects();
    }

    public TFSTeamProjectCollection getTeamProjectCollection(){
        return this.collection;
    }

    private void setTeamProjectCollectionInternal(TeamProjectCollectionInfo item){
        TFSTeamProjectCollection collection = null;
        //teamProjectTable.setProjects(null);
        teamProjectTable.clearProjects();
        if (configurationServer == null)
            return;

        if (item != null)
            collection = this.configurationServer.getTeamProjectCollection(item.getIdentifier());

        setTeamProjectCollectionInternal(collection);
    }

    private void setTeamProjectCollectionInternal(TFSTeamProjectCollection collection){
//        if (this.collection == collection)
//            return;
        this.collection = collection;
        this.teamProjects = null;
        this.selectedProjects = null;

        ProjectPreferences.getProjectInstance(myProject).setLastUsedProjectCollection(collection);

        refresh();
        skipCollectionSelection = false;
    }

    private void setServerInternal(URI serverURI, Credentials credentials){
        if ((this.serverURI != null) && (this.serverURI.equals(serverURI)) &&
                (this.credentials != null) && (this.credentials.equals(credentials)))
            return;

        this.serverURI = null;
        this.configurationServer = null;
        //this.projectCollections = null;
        this.collection = null;
        this.teamProjects = null;
        this.selectedProjects = null;

        this.projectCollectionTable.setTFSConnection(null);
        //this.teamProjectTable.setProjects(null);
        this.teamProjectTable.clearProjects();

        if (serverURI == null){
            refresh();
            return;
        }
        this.serverURI = serverURI;
        this.credentials = null;

        refresh();
    }

    private Runnable refreshProcess = new Runnable() {
        @Override
        public void run() {


            //if (this.lastSelectedProjects)
        }
    };

    private void refresh(){
        if (!hasPainted)
            return;

        if (selectedProjects != null && teamProjects != null && collection != null && /*projectCollections != null*/
                projectCollectionTable.getModel().getRowCount() ==0)
            refreshSelectedTeamProjects();
        else if (teamProjects == null && collection != null &&/* projectCollections != null*/
                projectCollectionTable.getModel().getRowCount() > 0)
            refreshTeamProjects();
        else if (/*projectCollections == null*/projectCollectionTable.getModel().getRowCount() ==0
                && configurationServer != null)
            refreshProjectCollections();
        else if (collection == null && configurationServer == null && serverURI != null)
            refreshConnection();
    }

    private void refreshSelectedTeamProjects(){
        this.selectedProjects = null;
        this.teamProjectTable.setCheckedProjects(null);
        if (this.teamProjects.length == 0)
            return;

        this.teamProjectTable.setCheckedProjects(this.selectedProjects);
        refresh();
    }

    private void refreshTeamProjects(){
        this.teamProjects = null;
        //this.teamProjectTable.setProjects(null);
        teamProjectTable.clearProjects();;

        this.teamProjects = this.teamProjectsForCollection.get(this.collection);
        if (this.teamProjects != null){
            teamProjectTable.setProjects(teamProjects);
            refresh();
            return;
        }

        QueryTeamProjectsTask queryCommand = new QueryTeamProjectsTask(this.myProject, this.collection);

        try{
            ProgressManager.getInstance().run(queryCommand);
        } catch(Throwable tr){
            FileLogger.getInstance(TeamProjectSelectControl.class).error(tr);
            this.teamProjectsForCollection.put(this.collection, new ProjectInfo[0]);
            return;
        }

        this.teamProjects = queryCommand.getProjects();
        this.teamProjectsForCollection.put(collection, teamProjects);
        teamProjectTable.setProjects(teamProjects);
        this.changeCredentials.setEnabled(true);
        refresh();
    }

    private void refreshProjectCollections(){
        //projectCollections = null;
        teamProjects = null;
        projectCollectionTable.setTFSConnection(null);
        //teamProjectTable.setProjects(null);
        teamProjectTable.clearProjects();;

        projectCollectionTable.setTFSConnection(configurationServer);
        if (this.collection != null)
            setProjectCollectionSelection(this.collection.getInstanceID());
        else if (this.configurationServer != null)
            setProjectCollectionSelection(getDefaultProjectCollection(this.configurationServer));

        this.changeCredentials.setEnabled(this.configurationServer != null);
        refresh();
    }

    private void refreshConnection(){
        this.collection = null;
        this.teamProjects = null;
        this.projectCollectionTable.setTFSConnection(null);
        //this.teamProjectTable.setProjects(null);
        teamProjectTable.clearProjects();
        this.changeCredentials.setEnabled(false);

        ConnectToConfigurationServerTask connectTask = new ConnectToConfigurationServerTask(this.myProject,
                this.serverURI, this.credentials);

        try{
            ProgressManager.getInstance().run(connectTask);
        } catch(Throwable tr){
            Messages.showErrorDialog(tr.getLocalizedMessage(), "Failed to establish connection");
        }
        if (connectTask.getConnection() != null){
            setConnectionInternal(connectTask.getConnection());
        }

    }


    private void setConnectionInternal(TFSConnection connection){
        if (connection instanceof TFSConfigurationServer){
            setConfigurationServer((TFSConfigurationServer)connection);
        } else if (connection instanceof TFSTeamProjectCollection){
            this.collection = (TFSTeamProjectCollection)connection;
            teamProjects = null;
            selectedProjects = null;
            refresh();
        }
    }

    private void setConfigurationServer(TFSConfigurationServer configurationServer){
        if (this.configurationServer == configurationServer)
            return;

        this.configurationServer = configurationServer;
        this.serverURI = configurationServer.getBaseURI();
        this.credentials = configurationServer.getCredentials();

        collection = null;
        teamProjects = null;
        selectedProjects = null;

        refresh();
    }

    private void setProjectCollectionSelection(GUID projectCollectionId){
        projectCollectionTable.setSelectedProjectCollection(projectCollectionId);
    }

    private GUID getDefaultProjectCollection(TFSConfigurationServer configurationServer){
        if (configurationServer == null)
            return null;
        return ProjectPreferences.getProjectInstance(myProject).getLastUsedProjectCollection(configurationServer);
    }

    private void useDifferentCredentials(){
        if (this.serverURI == null)
            return;
        Credentials credentials = (this.configurationServer != null) &&
                ((this.configurationServer.getCredentials() instanceof CookieCredentials)) ? new DefaultNTCredentials()
                : new UsernamePasswordCredentials("", null);
        if (this.configurationServer != null){
            this.configurationServer.getHTTPClient().getState().clearCookies();
            this.configurationServer.getHTTPClient().getState().clearCredentials();
            this.configurationServer.getCredentialsHolder().set(credentials);
        }

        setServerInternal(this.serverURI, credentials);
    }

    /**
     * Called when the hierarchy has been changed. To discern the actual type of change, call
     * <code>HierarchyEvent.getChangeFlags()</code>.
     *
     * @param e
     * @see HierarchyEvent#getChangeFlags()
     */
    @Override
    public void hierarchyChanged(HierarchyEvent e) {
        if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0)
            if (isShowing()){
                Runnable r = new Runnable() {
                    @Override
                    public void run() {
                        if (hasPainted)
                            return;

                        hasPainted = true;
                        /*if (!selectedFired) {
                            if (selectServerControl.getServerURI() != null) {
                                onItemSelected(null);
                                return;
                            }
                        }
                        if(initialServerURI != null)
                            setServer(initialServerURI, initialCredentials);
                        else*/
                        refresh();
                    }
                };
                r.run();
            }
    }

    @Override
    public void addNotify(){
        super.addNotify();
        addHierarchyListener(this);
    }

    @Override
    public void removeNotify(){
        removeHierarchyListener(this);
        super.removeNotify();
    }


    private class RefreshWorker extends SwingWorker<Void, Void>{

        /**
         * Computes a result, or throws an exception if unable to do so.
         * <p/>
         * <p/>
         * Note that this method is executed only once.
         * <p/>
         * <p/>
         * Note: this method is executed in a background thread.
         *
         * @return the computed result
         * @throws Exception if unable to compute a result
         */
        @Override
        protected Void doInBackground() throws Exception {
            publish((Void)null);
            refreshProcess.run();
            return null;
        }

        @Override
        protected void process(java.util.List<Void> chunks) {
            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

            if (myBackgroundProcessListener != null)
                myBackgroundProcessListener.processStarted();
        }

        @Override
        protected void done() {
            setCursor(Cursor.getDefaultCursor());
            if (myBackgroundProcessListener != null)
                myBackgroundProcessListener.processCompleted();
        }
    }

}


